package cn.silver.module.web.service;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.silver.framework.core.constant.GlobalConstants;
import cn.silver.framework.core.enums.SystemResultEnums;
import cn.silver.framework.core.enums.SystemStatusEnums;
import cn.silver.framework.core.exception.user.UserException;
import cn.silver.framework.core.utils.*;
import cn.silver.framework.log.event.LoginEvent;
import cn.silver.framework.redis.utils.RedisUtils;
import cn.silver.framework.satoken.model.LoginUser;
import cn.silver.framework.satoken.utils.LoginHelper;
import cn.silver.framework.tenant.exception.TenantException;
import cn.silver.framework.tenant.helper.TenantHelper;
import cn.silver.framework.web.enums.CaptchaTypeEnums;
import cn.silver.module.authority.bean.ClientBean;
import cn.silver.module.authority.bean.RoleBean;
import cn.silver.module.authority.bean.TenantBean;
import cn.silver.module.authority.domain.SysClient;
import cn.silver.module.authority.domain.SysRole;
import cn.silver.module.authority.domain.SysTenant;
import cn.silver.module.authority.param.TenantParam;
import cn.silver.module.authority.service.ISysClientService;
import cn.silver.module.authority.service.ISysRoleService;
import cn.silver.module.authority.service.ISysTenantService;
import cn.silver.module.system.bean.SocialBean;
import cn.silver.module.system.bean.UserBean;
import cn.silver.framework.mybatis.enums.RoleTypeEnums;
import cn.silver.module.system.models.RoleModel;
import cn.silver.module.system.service.ISysMenuService;
import cn.silver.module.system.service.ISysSocialService;
import cn.silver.module.system.service.ISysUserRoleService;
import cn.silver.module.web.enums.LoginType;
import cn.silver.module.web.model.AuthModel;
import cn.silver.module.web.model.CaptchaModel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 登录校验方法
 *
 * @author Silver Zhou
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysLoginService {

    private final ISysRoleService roleService;
    private final ISysMenuService menuService;
    private final ISysClientService clientService;
    private final ISysTenantService tenantService;
    private final ISysSocialService socialService;
    private final ISysUserRoleService userRoleService;
    private final SysCaptchaService captchaService;
    @Value("${user.password.maxRetryCount}")
    private Integer maxRetryCount;

    @Value("${user.password.lockTime}")
    private Integer lockTime;

    public AuthModel getAuthModel(String code, HttpServletRequest request) throws MalformedURLException {
        ClientBean client = clientService.selectOne(new LambdaQueryWrapper<SysClient>().eq(SysClient::getCode, code));
        AuthModel model = new AuthModel(client);
        CaptchaModel captchaModel = new CaptchaModel();
        captchaModel.setEnable(!CaptchaTypeEnums.NONE.getCode().equalsIgnoreCase(client.getCaptcha()));
        if (Objects.equals(Boolean.TRUE, captchaModel.getEnable())) {
            captchaModel = captchaService.generateCaptcha(CaptchaTypeEnums.getByCode(client.getCaptcha()));
        }
        model.setCaptcha(captchaModel);
        model.setTenant(TenantHelper.isEnable());
        if (Boolean.TRUE.equals(model.getTenant())) {
            List<TenantBean> models = tenantService.selectList(new TenantParam());
            if (CollectionUtils.isNotEmpty(models)) {
                String host;
                String referer = request.getHeader("referer");
                if (org.apache.commons.lang3.StringUtils.isNotBlank(referer)) {
                    // 这里从referer中取值是为了本地使用hosts添加虚拟域名，方便本地环境调试
                    host = referer.split("//")[1].split("/")[0];
                } else {
                    host = new URL(request.getRequestURL().toString()).getHost();
                }
                // 根据域名进行筛选
                List<TenantBean> filters = StreamUtils.filter(models, vo -> org.apache.commons.lang3.StringUtils.equals(vo.getDomain(), host));
                if (CollectionUtils.isNotEmpty(filters)) {
                    model.setTenants(filters);
                } else {
                    model.setTenants(models);
                }
            } else {
                model.setTenant(Boolean.FALSE);
            }
        }
        return model;
    }

    /**
     * 绑定第三方用户
     *
     * @param authUserData 授权响应实体
     * @return 统一响应实体
     */
    public void socialRegister(AuthUser authUserData) {
        String authId = authUserData.getSource() + authUserData.getUuid();
        // 第三方用户信息
        SocialBean model = BeanUtil.toBean(authUserData, SocialBean.class);
        BeanUtil.copyProperties(authUserData.getToken(), model);
        model.setUserId(LoginHelper.getUserId());
        model.setAuthId(authId);
        model.setOpenId(authUserData.getUuid());
        model.setUserName(authUserData.getUsername());
        model.setNickName(authUserData.getNickname());
        // 查询是否已经绑定用户
        SocialBean vo = socialService.selectById(authId);
        if (ObjectUtil.isEmpty(vo)) {
            // 没有绑定用户, 新增用户信息
            socialService.insert(model);
        } else {
            // 更新用户信息
            model.setId(vo.getId());
            socialService.update(model);
        }
    }


    /**
     * 退出登录
     */
    public void logout() {
        try {
            LoginUser loginUser = LoginHelper.getLoginUser();
            if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
                // 超级管理员 登出清除动态租户
                TenantHelper.clearDynamic();
            }
//			recordLogininfor(loginUser.getTenantId(), loginUser.getCode(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
        } catch (NotLoginException ignored) {
        } finally {
            try {
                StpUtil.logout();
            } catch (NotLoginException ignored) {
            }
        }
    }

    /**
     * 记录登录信息
     *
     * @param grantType 授权方式
     * @param tenantId  租户ID
     * @param user      登录用户
     * @param client    登录客户端
     * @param result    登录结果
     * @param message   消息内容
     */
    public void recordLogininfor(String grantType, String tenantId, UserBean user, ClientBean client, String result, String message) {
        LoginEvent loginEvent = new LoginEvent();
        loginEvent.setUserId(user.getId());
        loginEvent.setUserCode(user.getCode());
        loginEvent.setUserName(user.getName());
        loginEvent.setClientCode(client.getCode());
        loginEvent.setGrantType(grantType);
        loginEvent.setDeviceType(client.getDeviceType());
        loginEvent.setTenantId(tenantId);
        loginEvent.setResult(result);
        loginEvent.setMessage(message);
        loginEvent.setRequest(ServletUtils.getRequest());
        SpringUtils.context().publishEvent(loginEvent);
    }


    /**
     * 构建登录用户
     */
    public LoginUser buildLoginUser(UserBean user) {
        LoginUser loginUser = new LoginUser();
        loginUser.setId(user.getId());
        loginUser.setCode(user.getCode());
        loginUser.setName(user.getName());
        loginUser.setType(user.getType());
        loginUser.setDeptId(user.getDeptId());
        loginUser.setDeptName(user.getDeptName());
        loginUser.setIpaddr(ServletUtils.getClientIP());
        HttpServletRequest request = ServletUtils.getRequest();
        if (request != null) {
            UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
            loginUser.setBrowser(userAgent.getBrowser().getName());
            loginUser.setOs(userAgent.getOs().getName());
        }
        loginUser.setLoginTime(DateUtils.getNowDate());
        List<RoleBean> roles = this.userRoleService.selectRoleGroup(user.getId());
        if (CollectionUtils.isEmpty(roles)) {
            roles = roleService.selectList(new LambdaQueryWrapper<SysRole>().eq(SysRole::getType, RoleTypeEnums.DEFAULT_ROLE.getCode()));
        }
        loginUser.setRoleKeys(roles.stream().map(RoleBean::getCode).collect(Collectors.toSet()));
        loginUser.setPermission(menuService.selectLocalPermission(user.getId()));
        List<RoleModel> dtos = MapstructUtils.convert(roles, RoleModel.class);
        loginUser.setRoles(dtos);
        if (dtos != null) {
            loginUser.setRole(dtos.get(0));
        }
        return loginUser;
    }

    /**
     * 登录校验
     */
    public void checkLogin(LoginType loginType, String tenantId, UserBean model, ClientBean client, Supplier<Boolean> supplier) {
        String loginFail = SystemResultEnums.FAIL.getCode();

        // 获取用户登录错误次数，默认为0 (可自定义限制策略 例如: key + username + ip)
        int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(GlobalConstants.PWD_ERR_CNT_KEY, model.getCode()), 0);
        // 锁定时间内登录 则踢出
        if (errorNumber >= maxRetryCount) {
            recordLogininfor(loginType.getGrandType(), tenantId, model, client, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
            throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
        }

        if (supplier.get()) {
            // 错误次数递增
            errorNumber++;
            RedisUtils.setCacheObject(GlobalConstants.PWD_ERR_CNT_KEY, model.getCode(), errorNumber, Duration.ofMinutes(lockTime));
            // 达到规定错误次数 则锁定登录
            if (errorNumber >= maxRetryCount) {
                recordLogininfor(loginType.getGrandType(), tenantId, model, client, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
                throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
            } else {
                // 未达到规定错误次数
                recordLogininfor(loginType.getGrandType(), tenantId, model, client, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
                throw new UserException(loginType.getRetryLimitCount(), errorNumber);
            }
        }
        // 登录成功 清空错误次数
        RedisUtils.deleteObject(GlobalConstants.PWD_ERR_CNT_KEY, model.getCode());
    }

    /**
     * 校验租户
     *
     * @param tenantId 租户ID
     */
    public void checkTenant(String tenantId) {
        if (!TenantHelper.isEnable()) {
            return;
        }
        if (SysTenant.DEFALUT_CODE.equals(tenantId)) {
            return;
        }
        if (StringUtils.isBlank(tenantId)) {
            throw new TenantException("tenant.number.not.blank");
        }
        TenantBean tenant = tenantService.queryByTenantId(tenantId);
        if (ObjectUtil.isNull(tenant)) {
            log.info("登录租户：{} 不存在.", tenantId);
            throw new TenantException("tenant.not.exists");
        } else if (SystemStatusEnums.DISABLED.getCode().equals(tenant.getStatus())) {
            log.info("登录租户：{} 已被停用.", tenantId);
            throw new TenantException("tenant.blocked");
        } else if (ObjectUtil.isNotNull(tenant.getStopTime()) && new Date().after(tenant.getStopTime())) {
            log.info("登录租户：{} 已超过有效期.", tenantId);
            throw new TenantException("tenant.expired");
        }
    }

}
